/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023.
 * Description: ONT support gluebi mtd mounting
 * Author: Pang LiYuan
 * Create: 2023-09-11
 */
#ifdef CONFIG_RTOS_OPTIMIZE_ONT
/* Add interface to read NOR FLASH CHIPID */
static ssize_t mtd_chipid_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct mtd_info *mtd = dev_get_drvdata(dev);
	unsigned char ids[MTD_CHIP_ID_LEN] = {0};

	if ((mtd == NULL) || (mtd->read_chip_id == NULL))
		return 0;

	mtd->read_chip_id(mtd, ids, MTD_CHIP_ID_LEN);

	return snprintf(buf, PAGE_SIZE, "%02x%02x%02x%02x%02x%02x%02x%02x\n",
		ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7]);
}
static DEVICE_ATTR(chipid, 0440, mtd_chipid_show, NULL);
#endif

#ifdef CONFIG_RTOS_ONT_RAW_MTD
int find_raw_mtd(struct mtd_info *mtd)
{
	int i = 0;

	for (i = 0; i < specific_raw_mtd_count; i++)
		if (specific_raw_mtd[i] == mtd->index)
			return 1;

	return 0;
}

int count_bad_blocks(struct mtd_info *mtd)
{
	loff_t offs = 0;
	int badcnt = 0;

	while (offs < mtd->size) {
		if (mtd_block_isbad(mtd, offs))
			badcnt++;
		offs += mtd->erasesize;
	}

	return badcnt;
}

void update_block_map(struct mtd_info *mtd)
{
	int i = 0, j = 0;
	loff_t offs = 0;

	while (offs < mtd->size) {
		if (mtd_block_isbad(mtd, offs)) {
			j++;
			offs += mtd->erasesize;
			continue;
		}

		mtd->mtd_block_map.leb[i] = j;
		i++;
		j++;
		offs += mtd->erasesize;
	}

	while (i < mtd->blk_cnt) {
		mtd->mtd_block_map.leb[i] = -1;
		i++;
	}
}

loff_t find_phy_offs(struct mtd_info *mtd, loff_t offs)
{
	int pnum = 0;
	uint64_t lnum = offs;
	loff_t phy_offs = 0;

	if (offs < 0 || offs > mtd->size)
		return -EINVAL;

	do_div(lnum, mtd->erasesize);
	pnum = mtd->mtd_block_map.leb[lnum];
	if (pnum == -1) {
		pr_err("raw_mtd: mtd%d-leb:%lld don't map to a peb, it has %d bad blocks.\n",
			mtd->index, lnum, count_bad_blocks(mtd));
		return -EINVAL;
	}

	phy_offs = ((loff_t)pnum) * mtd->erasesize;
	return phy_offs;
}

int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
	     u_char *buf)
{
	int ret_code;
	size_t readlen;
	size_t retlen_tmp = 0;
	int is_raw_mtd = find_raw_mtd(mtd);
	int mod = 0;
	struct mtd_oob_ops ops = {0};

	*retlen = 0;

	if (is_raw_mtd) {
		mod = (unsigned int)from & (mtd->erasesize - 1);
		from = find_phy_offs(mtd, from);
		if (from == -EINVAL)
			return -EINVAL;
		from = from + mod;
	}

	if (from < 0 || from >= mtd->size || len > mtd->size - from)
		return -EINVAL;
	if (!len)
		return 0;

	if (!is_raw_mtd) {
		ops.len = len;
		ops.datbuf = buf;
		ret_code = mtd_read_oob(mtd, from, &ops);
		*retlen = ops.retlen;
		if (unlikely(ret_code < 0))
			return ret_code;
		if (mtd->ecc_strength == 0)
			return 0;
		return ret_code >= mtd->bitflip_threshold ? -EUCLEAN : 0;
	}

	while ((len > 0) && (from < mtd->size)) {
		if (len > (mtd->erasesize - mod))
			readlen = mtd->erasesize - mod;
		else
			readlen = len;

		ops.len = readlen;
		ops.datbuf = buf;
		ret_code = mtd_read_oob(mtd, from, &ops);
		*retlen = ops.retlen;
		if (unlikely(ret_code < 0))
			return ret_code;
		if ((mtd->ecc_strength != 0) && (ret_code >= mtd->bitflip_threshold))
			return -EUCLEAN;

		len -= *retlen;
		buf += *retlen;
		retlen_tmp += *retlen;

		if (mod) {
			from += readlen;
			mod = 0;
		} else {
			from += mtd->erasesize;
		}
	}
	*retlen = retlen_tmp;
	if (len == 0)
		return 0;
	printk("raw_mtd: mtd%d dont have so much data to read, left len:%d.\n", mtd->index, len);
	return -ENOSPC;
}

int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
	      const u_char *buf)
{
	struct mtd_oob_ops ops = {
		.len = len,
		.datbuf = (u8 *)buf,
	};
	int ret;

	int is_raw_mtd = find_raw_mtd(mtd);
	*retlen = 0;

	if (is_raw_mtd) {
		if (((unsigned int)to & (mtd->erasesize - 1)) && (len != mtd->erasesize)) {
			printk("raw_mtd: mtd%d write args to or len is invalid.\n", mtd->index);
			return -EINVAL;
		}
		to = find_phy_offs(mtd, to);
		if (to == -EINVAL)
			return -EINVAL;
	}

	if (to < 0 || to >= mtd->size || len > mtd->size - to)
		return -EINVAL;
	if (!(mtd->flags & MTD_WRITEABLE))
		return -EROFS;
	if (!len)
		return 0;

	ret = mtd_write_oob(mtd, to, &ops);
	*retlen = ops.retlen;

	return ret;
}
#endif